import {LoaderStatus, LoaderErrors, BaseLoader} from './loader'

class FetchStreamLoader extends BaseLoader {
    static isSupported() {
        return true
    }

    constructor(seekHandler, config) {
        super('fetch-stream-loader')
        this._seekHandler = seekHandler
        this._config = config
        this._needStash = true

        this._requestAbort = false
        this._contentLength = null
        this._receivedLength = 0
    }

    destroy() {
        if (this.isWorking()) {
            this.abort()
        }
        super.destroy()
    }
 
    abort() {
        this._requestAbort = true
    }

    open(dataSource, range) {
        this._dataSource = dataSource
        this._range = range

        let sourceURL = dataSource.url
        if (
            this._config.reuseRedirectedURL &&
            dataSource.redirectedURL != undefined
        ) {
            sourceURL = dataSource.redirectedURL
        }

        let seekConfig = this._seekHandler.getConfig(sourceURL, range)
        
        let heades = new self.Headers()

        if (typeof seekConfig.headers === 'object') {
            let configHeaders = seekConfig.headers
            for (let key in configHeaders) {
                if (configHeaders.hasOwnProperty(key)) {
                    headers.append(key, configHeaders[key])
                }
            }
        }

        let params = {
            method: 'GET',
            headers: headers,
            mode: 'cors',
            cache: 'default',
            referrerPolicy: 'no-referrer-when-downgrade'
        }

        // add additional headers
        if (typeof this._config.headers === 'object') {
            for (let key in this._config.headers) {
                headers.append(key, this._config.headers[key])
            }
        }

        // cors is enabled by default
        if (dataSource.cors === false) {
            params.mode = 'same-origin'
        }

        // withCredentials is disabled by default
        if (dataSource.withCredentials) {
            params.credentials = 'include'
        }

        // referrerPolicy from config
        if (dataSource.referrerPolicy) {
            params.referrerPolicy = dataSource.referrerPolicy
        }

        this._status = LoaderStatus.kConnecting
        self.fetch(seekConfig.url, params).then((res) => {
            if (this._requestAbort) {
                this._requestAbort = false
                this._status = LoaderStatus.kIdle
                return
            }
            if (res.ok && (res.status >= 200 && res.status <= 299)) {
                if (res.url !== seekConfig.url) {
                    if (this._onURLRedirect) {
                        let redirectedURL = this._seekHandler.removeURLParameters(res.url)
                        this._onURLRedirect(redirectedURL)
                    }
                }

                let lengthHeader = res.headers.get('Content-Length')
                if (lengthHeader != null) {
                    this._contentLength = parseInt(lengthHeader)
                    if (this._contentLength !== 0) {
                        if (this._onContentLengthKnown) {
                            this._onContentLengthKnown(this._contentLength)
                        }
                    }
                }
                this._pump.call(this, res.body.getReader())
            } else {
                this._status = LoaderStatus.kError
                if (this._onError) {
                    this._onError(LoaderErrors.HTTP_STATUS_CODE_INVALID, {
                        code: res.status,
                        msg: res.statusText
                    })
                } else {
                    throw new Error('FetchStreamLoader: Http code invalid, ' + res.status + ' ' + res.statusText)
                }
            }
        }).catch((e) => {
            this._status = LoaderStatus.kError
            if (this._onError) {
                this._onError(LoaderErrors.EXCEPTION, {
                    code: -1, msg: e.message
                })
            } else {
                throw e
            }
        })
    }

    _pump(reader) { // ReadableStreamReader
        return reader.read().then(result => {
            if (result.done) {
                // First check received length
                if (
                    this._contentLength !== null &&
                    this._receivedLength < this._contentLength
                ) {
                    // Report Early-EOF
                    this._status = LoaderStatus.kError
                    let type = LoaderErrors.EARLY_EOF
                    let info = {code: -1, msg: 'Fetch stream meet Early-EOF'}
                    if (this._onError) {
                        this._onError(type, info)
                    } else {
                        throw new Error(info.msg)
                    }
                } else {
                    // OK. Download complete
                    this._status = LoaderStatus.kComplete
                    if (this._onComplete) {
                        this._onComplete(
                            this._range.from,
                            this._range.from + this._receivedLength - 1
                        )
                    }
                }
            } else {
                if (this._requestAbort === true) {
                    this._requestAbort = false
                    this._status = LoaderStatus.kComplete
                    return reader.cancel()
                }

                this._status = LoaderStatus.kBuffering
                let chunk = result.value.buffer
                let byteStart = this._range.from + this._receivedLength
                this._receivedLength += chunk.byteLength

                if (this._onDataArrival) {
                    this._onDataArrival(chunk, byteStart, this._receivedLength)
                }
                this._pump(reader)
            }
        }).catch((e) => {
            this._status = LoaderStatus.kError
            let type = 0
            let info = null

            if (
                (e.code === 19 || e.message === 'network error') &&
                // NETWORK_ERR
                (
                    this._contentLength === null ||
                    (
                        this._contentLength !== null &&
                        this._receivedLength < this._contentLength
                    )
                )
            ) {
                type = LoaderErrors.EARLY_EOF
                info = {code: e.code, msg: 'Fetch stream meet Early-EOF'}
            }

            if (this._onError) {
                this._onError(type, info)
            } else {
                throw new Error(info.msg)
            }
        })
    }
}

export default FetchStreamLoader